/*
 *
 */
package org.jiscinvolve.astrodabis.barberry.registry;

import java.net.URL;
import java.net.MalformedURLException;

import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import java.io.FileReader;
import java.io.StringReader;
import java.io.BufferedReader;
import java.io.LineNumberReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;

import java.util.List;
import java.util.ArrayList;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 */
public class RegistryHarvester
    {

    /**
     * Our debug logger.
     * 
     */
    protected static Log log = LogFactory.getLog(RegistryHarvester.class);

    protected static final String REG_QUERY =
          " for $x in //RootResource "
        + " where exists($x/capability[@standardID='ivo://ivoa.net/std/TAP']/interface/accessURL) and $x/@status='active'"
        + " return"
        + " &lt;service&gt;"
        + "    &lt;title&gt;{$x/title}&lt;/title&gt;"
        + "    &lt;owner&gt;{$x/curation/publisher}&lt;/owner&gt;"
        + "    &lt;ivorn&gt;{$x/identifier}&lt;/ivorn&gt;"
        + "    &lt;ivoa-tap&gt;"
        + "        {$x/capability[@standardID='ivo://ivoa.net/std/TAP']/interface/accessURL}"
        + "    &lt;/ivoa-tap&gt;"
        + "    &lt;vosi-cap&gt;"
        + "        {$x/capability[@standardID='ivo://ivoa.net/std/VOSI#capabilities']/interface/accessURL}"
        + "        {$x/capability[@standardID='ivo://org.astrogrid/std/VOSI/v0.3#capabilities']/interface/accessURL}"
        + "    &lt;/vosi-cap&gt;"
        + "    &lt;vosi-tab&gt;"
        + "        {$x/capability[@standardID='ivo://ivoa.net/std/VOSI#tables']/interface/accessURL}"
        + "        {$x/capability[@standardID='ivo://org.astrogrid/std/VOSI/v0.3#tables']/interface/accessURL}"
        + "    &lt;/vosi-tab&gt;"
        + "&lt;/service&gt;"
        + "";

    public RegistryHarvester()
        {
        }

    public void harvest(String endpoint)
        {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(false);

		    DocumentBuilder builder = factory.newDocumentBuilder();

		    Document dom = builder.parse(
		        new InputSource(
		            new StringReader(
                        post(
                            endpoint,
                            REG_QUERY
                            )
                        )
                    )
		        );

		    services(
		        dom.getDocumentElement()
		        );
            }
        catch (Exception ouch)
            {
            log.debug("Failed to query registry [" + ouch + "]");
            }
        }

    public String post(String endpoint, String query)
        {
        PostMethod post = new PostMethod(
            endpoint
            );
        post.setFollowRedirects(
            false
            ); 
        post.setParameter(
            "performquery",
            "true"
            );
        post.setParameter(
            "ResourceXQuery",
            query
            );

        try {
            HttpClient httpclient = new HttpClient();
            httpclient.executeMethod(
                post
                );
            int status = post.getStatusCode();
            String result = post.getResponseBodyAsString();
            post.releaseConnection();
            return result ;

            }
        catch(Exception ouch)
            {
            log.debug("Failed to query registry [" + ouch + "]");
            return null ;
            }
        }

    private List<Service> services = new ArrayList<Service>();
    public  Iterable<Service> services()
        {
        return this.services;
        }
    
    public void services(Element element)
        {
        NodeList nodes = element.getElementsByTagName("service");

		for(int i = 0 ; i < nodes.getLength() ; i++)
		    {
		    services.add(
		        new Service(
		            (Element) nodes.item(i)
    	            )
	            );
			}
        }

    public class Service
        {

        public Service(Element element)
            {

            this.title(element);
            this.ivorn(element);

            this.owner = new Publisher(element);

            this.tap = new Endpoint("ivoa-tap", element);
            this.cap = new Endpoint("vosi-cap", element);
            this.tab = new Endpoint("vosi-tab", element);

            if (this.tab.url() != null)
                {
                this.tables(this.tab.url());
                }
            else if ("base".equals(this.tap.use()))
                {
                if (this.tap.url() != null)
                    {
                    this.tables(this.tap.url() + "/tables");
                    }
                }
            }

        public String toString()
            {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\n");
            buffer.append("Service{");

            buffer.append(" ");
            buffer.append("title{");
            buffer.append(this.title);
            buffer.append("}");

            buffer.append(" ");
            buffer.append("ivorn{");
            buffer.append(this.ivorn);
            buffer.append("}");

            buffer.append("\n");
            buffer.append(this.owner);

            buffer.append("\n");
            buffer.append(this.tap);

            buffer.append("\n");
            buffer.append(this.cap);

            buffer.append("\n");
            buffer.append(this.tab);
/*
            for(Schema schema : schemas)
                {
                buffer.append("\n");
                buffer.append(schema);
                }
 */
            buffer.append("}");
            return buffer.toString();
            }

        private Endpoint tap ;
        public  Endpoint tap()
            {
            return this.tap;
            }

        private Endpoint cap ;
        public  Endpoint cap()
            {
            return this.cap;
            }

        private Endpoint tab ;
        public  Endpoint tab()
            {
            return this.cap;
            }

        private String ivorn ;
        public  String ivorn()
            {
            return this.ivorn;
            }

        private String title ;
        public  String title()
            {
            return this.title;
            }

        private Publisher owner ;
        public  Publisher owner()
            {
            return this.owner;
            }

        protected void title(Element element)
            {
            NodeList outer = element.getElementsByTagName("title");
            if (outer.getLength() > 0)
                {
                NodeList inner = ((Element) outer.item(0)).getElementsByTagName("title");
                if (inner.getLength() > 0)
                    {
                    this.title = inner.item(0).getTextContent();
                    }
                }
            }

        protected void ivorn(Element element)
            {
            NodeList outer = element.getElementsByTagName("ivorn");
            if (outer.getLength() > 0)
                {
                NodeList inner = ((Element) outer.item(0)).getElementsByTagName("identifier");
                if (inner.getLength() > 0)
                    {
                    this.ivorn = inner.item(0).getTextContent();
                    }
                }
            }

        public class Publisher
            {
            public Publisher(Element element)
                {
                NodeList outer = element.getElementsByTagName("owner");
                if (outer.getLength() > 0)
                    {
                    NodeList inner = ((Element) outer.item(0)).getElementsByTagName("publisher");
                    if (inner.getLength() > 0)
                        {
                        NamedNodeMap map = inner.item(0).getAttributes();
                        if (map != null)
                            {
                            Node attrib = map.getNamedItem("ivo-id");
                            if (attrib != null)
                                {
                                this.uri = attrib.getNodeValue();
                                }
                            }
                        this.name = inner.item(0).getTextContent();
                        }
                    }
                }

            private String name ;
            public  String name()
                {
                return this.name ;
                }

            private String uri ;
            public  String uri()
                {
                return this.uri ;
                }

            public String toString()
                {
                StringBuffer buffer = new StringBuffer();
                buffer.append("Publisher{");

                buffer.append(" ");
                buffer.append("name{");
                buffer.append(this.name);
                buffer.append("}");

                buffer.append(" ");
                buffer.append("uri{");
                buffer.append(this.uri);
                buffer.append("}");

                buffer.append("}");
                return buffer.toString();
                }

            }

        public class Endpoint
            {

            public Endpoint(String type, Element element)
                {
                this.type = type ;

                NodeList outer = element.getElementsByTagName(type);
                if (outer.getLength() > 0)
                    {
                    NodeList inner = ((Element) outer.item(0)).getElementsByTagName("accessURL");
                    if (inner.getLength() > 0)
                        {
                        NamedNodeMap map = inner.item(0).getAttributes();
                        if (map != null)
                            {
                            Node attrib = map.getNamedItem("use");
                            if (attrib != null)
                                {
                                this.use = attrib.getNodeValue();
                                }
                            }
                        this.url = inner.item(0).getTextContent();
                        }
                    }
                }

            private String type ;
            public String type()
                {
                return this.type ;
                }

            private String use ;
            public String use()
                {
                return this.use ;
                }

            private String url ;
            public String url()
                {
                return this.url ;
                }

            public String toString()
                {
                StringBuffer buffer = new StringBuffer();
                buffer.append("Endpoint{");

                buffer.append(" ");
                buffer.append("type{");
                buffer.append(this.type);
                buffer.append("}");

                buffer.append(" ");
                buffer.append("use{");
                buffer.append(this.use);
                buffer.append("}");

                buffer.append(" ");
                buffer.append("url{");
                buffer.append(this.url);
                buffer.append("}");

                buffer.append("}");
                return buffer.toString();
                }

            }

        public void tables(String url)
            {
            try {
                this.tables(
                    new URL(
                        url
                        )
                    );
                }
            catch (MalformedURLException ouch)
                {
                log.error("URL error [" + url + "]");
                }
            }

        public void tables(URL url)
            {
            try {

                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setNamespaceAware(true);

		        DocumentBuilder builder = factory.newDocumentBuilder();

		        Document dom = builder.parse(
		            new InputSource(
                        new InputStreamReader(
                            url.openStream()
                            )
                        )
                    );

    		    this.schemas(
    		        dom.getDocumentElement()
    		        );

                }
            catch (IOException ouch)
                {
                log.error("Error reading tables [" + ouch.getMessage() + "][" + ouch.getClass() + "]");
                }
            catch (SAXException ouch)
                {
                log.error("Error reading tables [" + ouch.getMessage() + "][" + ouch.getClass() + "]");
                }
            catch (ParserConfigurationException ouch)
                {
                log.error("Error reading tables [" + ouch.getMessage() + "][" + ouch.getClass() + "]");
                }

            }

        public void schemas(Element element)
            {
            NodeList nodes = element.getElementsByTagName("schema");
            if (nodes.getLength() > 0)
                {
                for(int i = 0 ; i < nodes.getLength() ; i++)
                    {
                    schemas.add(
                        new Schema(
                            (Element) nodes.item(i)
                            )
                        );
                    }
                }
            else if ("tables".equals(element.getLocalName()))
                {
                schemas.add(
                    new Schema(
                        "default",
                        element
                        )
                    );
                }
            }

        private List<Schema> schemas = new ArrayList<Schema>();
        public  Iterable<Schema> schemas()
            {
            return this.schemas ;
            }

        public class Schema
            {

            public Schema(String name, Element element)
                {
                this.name(name) ;
                this.tables(element);
                }

            public Schema(Element element)
                {
                this.name(element);
                this.tables(element);
                }

            private String name ;
            public  String name()
                {
                return this.name ;
                }

            private void name(String name)
                {
                this.name = name ;
                }

            private void name(Element element)
                {
                NodeList nodes = element.getElementsByTagName("name");
                if (nodes.getLength() > 0)
                    {
                    this.name = nodes.item(0).getTextContent();
                    }
                }

            private void tables(Element element)
                {
                NodeList nodes = element.getElementsByTagName("table");
                for(int i = 0 ; i < nodes.getLength() ; i++)
                    {
                    this.tables.add(
                        new Table(
                            (Element) nodes.item(i)
                            )
                        );
                    }
                }

            public String toString()
                {
                StringBuffer buffer = new StringBuffer();
                buffer.append("Schema{");

                buffer.append(" ");
                buffer.append("name{");
                buffer.append(this.name);
                buffer.append("}");
/*
                for(Table table : tables)
                    {
                    buffer.append("\n");
                    buffer.append(table);
                    }
 */
                buffer.append("}");
                return buffer.toString();            
                }

            private List<Table> tables = new ArrayList<Table>();
            public  Iterable<Table> tables()
                {
                return this.tables ;
                }

            public class Table
                {
                public Table(Element element)
                    {
                    this.name(element);
                    this.columns(element);
                    }

                private String name ;
                public  String name()
                    {
                    return this.name ;
                    }

                private void name(Element element)
                    {
                    NodeList nodes = element.getElementsByTagName("name");
                    if (nodes.getLength() > 0)
                        {
                        this.name = nodes.item(0).getTextContent();
                        }
                    }

                public String toString()
                    {
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("Table{");

                    buffer.append(" ");
                    buffer.append("name{");
                    buffer.append(this.name);
                    buffer.append("}");
/*
                    for(Column column : columns)
                        {
                        buffer.append("\n");
                        buffer.append(column);
                        }
 */
                    buffer.append("}");
                    return buffer.toString();            
                    }

                private void columns(Element element)
                    {
                    NodeList nodes = element.getElementsByTagName("column");
                    for(int i = 0 ; i < nodes.getLength() ; i++)
                        {
                        this.columns.add(
                            new Column(
                                (Element) nodes.item(i)
                                )
                            );
                        }
                    }

                private List<Column> columns = new ArrayList<Column>();
                public  Iterable<Column> columns()
                    {
                    return this.columns ;
                    }

                public class Column
                    {
                    private Column(Element element)
                        {
                        this.name(element);
                        }

                    private String name ;
                    public  String name()
                        {
                        return this.name ;
                        }

                    private void name(Element element)
                        {
                        NodeList nodes = element.getElementsByTagName("name");
                        if (nodes.getLength() > 0)
                            {
                            this.name = nodes.item(0).getTextContent();
                            }
                        }

                    public String toString()
                        {
                        StringBuffer buffer = new StringBuffer();
                        buffer.append("Column{");

                        buffer.append(" ");
                        buffer.append("name{");
                        buffer.append(this.name);
                        buffer.append("}");

                        buffer.append("}");
                        return buffer.toString();            
                        }
                    }
                }
            }
        }

    }

